# server/riskcontroller.py
import traceback
from datetime import datetime, date
import pytz
import akshare as ak
from flask import Blueprint, request, jsonify, current_app
from flask_jwt_extended import jwt_required, get_jwt_identity

from app.models import (
    db,
    UserAsset,
    AssetTypeEnum,
    Preference,
    AssetRiskCache
)


risk_controller = Blueprint('risk_controller', __name__)

from datetime import datetime, time
import pytz

def is_market_open():
    # 北京时间
    tz = pytz.timezone('Asia/Shanghai')
    now = datetime.now(tz)
    # 周一=0 … 周五=4
    if now.weekday() > 4:
        return False
    t = now.time()
    return (time(9,0) <= t < time(11,0)) or (time(13,0) <= t < time(15,0))


@risk_controller.route('/scores', methods=['GET'])
@jwt_required()
def get_risk_scores():
    """
    GET /risk/scores?userid=<用户ID>
    批量返回该用户所有资产的风险评分
    依赖：UserAsset 表中的 quantity, buy_price
    Dummy 算法：cost = quantity * buy_price
                 risk_score = min(1, cost / 100000)
    """
    try:
        userid = get_jwt_identity()
        if not userid:
            return jsonify({'code': 1, 'msg': '缺少 userid 参数'}), 400

        assets = UserAsset.query.filter_by(userid=userid).all()
        scores = []
        for a in assets:
            cost = a.quantity * a.buy_price
            score = cost / 100000.0
            score = max(0.0, min(1.0, score))
            scores.append({
                'id': a.id,
                'risk_score': round(score, 4)
            })

        return jsonify({'code': 0, 'data': scores}), 200

    except Exception as e:
        current_app.logger.error(f"[RISK] 批量计算风险失败: {e}")
        current_app.logger.error(traceback.format_exc())
        return jsonify({'code': 1, 'msg': '批量计算风险失败'}), 500


@risk_controller.route('/asset/<int:asset_id>', methods=['GET'])
def get_asset_risk_detail(asset_id):
    """
    GET /risk/asset/<asset_id>
    返回单条资产的风险评分及因子拆解
    Dummy 因子：成本市值
    """
    try:
        asset = UserAsset.query.get(asset_id)
        if not asset:
            return jsonify({'code': 1, 'msg': '资产不存在'}), 404

        cost = asset.quantity * asset.buy_price
        score = cost / 100000.0
        score = max(0.0, min(1.0, score))
        score = round(score, 4)

        factors = {
            '成本市值': round(cost, 2)
        }

        detail = {
            'id': asset.id,
            'type': asset.type.value,
            'products_id': asset.products_id,
            'quantity': asset.quantity,
            'buy_price': asset.buy_price,
            'risk_score': score,
            'factors': factors
        }
        return jsonify({'code': 0, 'data': detail}), 200

    except Exception as e:
        current_app.logger.error(f"[RISK] 获取资产风险详情失败: {e}")
        current_app.logger.error(traceback.format_exc())
        return jsonify({'code': 1, 'msg': '获取资产风险详情失败'}), 500
    

# 风险偏好映射
PREF_MAP = {'低风险': 0.3, '中风险': 0.5, '高风险': 0.8}

# 获取“今天”在北京时区的日期
def today_sh():
    return datetime.now(pytz.timezone('Asia/Shanghai')).date()

@risk_controller.route('/stock_scores', methods=['GET'])
@jwt_required()
def get_stock_risk_scores():
    """
    GET /api/risk/stock_scores
    每天只计算一次，并写入 asset_risk_cache；当天直接读缓存。
    """
    try:
        user_id = get_jwt_identity()

        # 1. 用户偏好容忍度
        pref = Preference.query.filter_by(userid=user_id).first()
        tol = PREF_MAP.get(pref.riskpreference.value, 0.5) if pref else 0.5

        # 2. 用户股票持仓
        assets = UserAsset.query.filter_by(
            userid=user_id,
            type=AssetTypeEnum.股票
        ).all()
        if not assets:
            return jsonify({'code': 0, 'data': []}), 200

        # 3. 先从缓存表取出今天已计算的
        td = today_sh()
        cached = AssetRiskCache.query.filter(
            AssetRiskCache.asset_id.in_([a.id for a in assets]),
            AssetRiskCache.computed_at == td
        ).all()
        cache_map = {c.asset_id: c for c in cached}

        # 如果缓存数等于资产数，直接返回缓存
        if len(cache_map) == len(assets):
            data = [
                {'id': aid, 
                 'products_id': c.asset_id,
                 'risk_score': float(c.risk_score),
                 'asset_name': str(c.asset_name),
                 'amp':float(c.amp),
                 'turn':float(c.turn),
                 'latest_price':c.latest_price,
                 'buy_price':c.buy_price,
                 'pos':c.pos,
                 'quantity':c.quantity
                 }
                for aid, c in cache_map.items()
            ]
            return jsonify({'code': 0, 'data': data}), 200

        # 否则——第一次或尚未更新，用实时行情计算一次并写入缓存
        df = ak.stock_zh_a_spot_em()

        # 4. 计算组合总市值
        total_mv = 0
        rows = []
        for a in assets:
            code = a.products_id.split('.')[0]
            row = df[df['代码'] == code]
            if row.empty:
                continue
            price = float(row['最新价'].iloc[0])
            mv = price * a.quantity
            total_mv += mv
            rows.append((a, row.iloc[0], price, mv))

        # 5. 逐条计算并 upsert 缓存
        for a, info, price, mv in rows:
            amp  = float(info['振幅'])   / 100.0
            turn = float(info['换手率']) / 100.0
            pos  = mv / total_mv if total_mv > 0 else 0.0

            raw = max(0, min(1, 0.4*amp + 0.3*turn + 0.3*pos))
            match = 1 - abs(raw - tol)
            score = round(match, 4)

            if a.id in cache_map:
                c = cache_map[a.id]
                c.asset_name=a.products_name
                c.risk_score  = score
                c.amp=amp
                c.turn=turn
                c.latest_price=price
                c.buy_price=a.buy_price
                c.pos=pos
                c.quantity=a.quantity
                
            else:
                c = AssetRiskCache(
                    asset_id    = a.id,
                    risk_score  = score,
                    asset_name  = a.products_name,
                    computed_at = td
                )
                db.session.add(c)

        db.session.commit()



        # 6. 再次读取今天的缓存并返回
        fresh = AssetRiskCache.query.filter(
            AssetRiskCache.asset_id.in_([a.id for a in assets]),
            AssetRiskCache.computed_at == td
        ).all()
        data = [{'id': c.asset_id, 'risk_score': float(c.risk_score),'asset_name':str(c.asset_name),'amp':float(c.amp),'turn':float(c.turn),'latest_price':c.latest_price,'buy_price':c.buy_price,'pos':c.pos,'quantity':c.quantity} for c in fresh]
        return jsonify({'code': 0, 'data': data}), 200

    except Exception as e:
        current_app.logger.error(f"[RISK] 计算股票风险失败: {e}")
        current_app.logger.error(traceback.format_exc())
        return jsonify({'code': 1, 'msg': '计算股票风险失败'}), 500
    
@risk_controller.route('/stock/<int:asset_id>', methods=['GET'])
@jwt_required()
def get_stock_risk_detail(asset_id):
    """
    GET /api/risk/stock/<asset_id>
    返回单条股票持仓的异构因子和最终风险评分
    """
    try:
        user_id = get_jwt_identity()
        pref = Preference.query.filter_by(userid=user_id).first()
        tol = PREF_MAP.get(pref.riskpreference.value, 0.5) if pref else 0.5

        asset = UserAsset.query.get(asset_id)
        if not asset or asset.type != AssetTypeEnum.股票:
            return jsonify({'code': 1, 'msg': '资产不存在或非股票'}), 404

        df = ak.stock_zh_a_spot_em()
        code = asset.products_id.split('.')[0]
        row = df[df['代码'] == code]
        if row.empty:
            return jsonify({'code': 1, 'msg': '无法获取行情'}), 200
        info = row.iloc[0]

        # 基本信息
        stock_name = info['名称']
        latest_price = float(info['最新价'])
        mv = latest_price * asset.quantity

        # 特征因子
        amp = float(info['振幅']) / 100.0
        turn = float(info['换手率']) / 100.0
        # 重新计算组合总市值以准确得出仓位占比
        all_assets = UserAsset.query.filter_by(userid=user_id, type=AssetTypeEnum.股票).all()
        total_mv = 0
        for a2 in all_assets:
            c2 = a2.products_id.split('.')[0]
            r2 = df[df['代码'] == c2]
            if not r2.empty:
                total_mv += float(r2['最新价'].iloc[0]) * a2.quantity
        pos = mv / total_mv if total_mv > 0 else 0

        # 计算
        raw_score = 0.4 * amp + 0.3 * turn + 0.3 * pos
        raw_score = max(0, min(1, raw_score))
        risk_score = round(1 - abs(raw_score - tol), 4)

        # 返回结构
        factors = {
            '振幅因子': amp,
            '换手率因子': turn,
            '仓位占比因子': pos,
            '用户风险容忍度': tol
        }
        data = {
            'id': asset.id,
            'products_id': asset.products_id,
            'products_name': stock_name,
            'latest_price': latest_price,
            'quantity': asset.quantity,
            'buy_price': asset.buy_price,
            'risk_score': risk_score,
            'factors': factors
        }
        current_app
        return jsonify({'code': 0, 'data': data}), 200

    except Exception as e:
        current_app.logger.error(f"[RISK] 获取股票风险详情失败: {e}")
        current_app.logger.error(traceback.format_exc())
        return jsonify({'code': 1, 'msg': '获取详情失败'}), 500
